package com.jacob.androidframe.controls;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

import org.acra.ACRA;
import org.acra.ReportField;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import org.acra.collector.CrashReportData;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;
import org.androidannotations.annotations.EApplication;
import org.androidannotations.annotations.SystemService;
import org.slf4j.LoggerFactory;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.telephony.TelephonyManager;
import android.text.TextUtils;

/**
 * Android程序中访问资源时需要提供Context，一般来说只有在各种组件中（Activity,
 * Provider等等)才能方便的使用api来获取Context，而在某些工具类中要获取就很麻烦了。
 * 为此，我们可以自定义一个Application类来实现这种功能。
 * 在manifest中，将<application>标签的name改为本类。
 * 然后调用ApplicationExtend.getInstance()方法，即可得到ApplicationContext对象。
 * 此对象在使用时要小心，具体查看getInstance()方法的注释。
 * 
 * @author 王超
 */
@ReportsCrashes(formKey = "",
		mode = ReportingInteractionMode.SILENT,
		customReportContent = {
				ReportField.REPORT_ID,
				ReportField.APP_VERSION_CODE,
				ReportField.APP_VERSION_NAME,
				ReportField.PACKAGE_NAME,
				ReportField.CUSTOM_DATA,
				ReportField.USER_APP_START_DATE,
				ReportField.USER_CRASH_DATE,
				ReportField.USER_COMMENT,
				ReportField.STACK_TRACE,
				ReportField.BRAND,
				ReportField.PHONE_MODEL,
				ReportField.USER_IP,
				ReportField.ANDROID_VERSION
		})
@EApplication
public class ApplicationExtend extends Application
{
	private static ApplicationExtend mSingleton;
	private List<WeakReference<Activity>> mActivityList;

	private int mVersionCode;
	private String mVersionName;
	private String mIMEIorMEIDorESN;
	private String mIMEISV;
	private String mIMSI;

	@SystemService
	TelephonyManager mTelephonyManager;

	@Override
	public void onCreate()
	{
		super.onCreate();

		ACRA.init(this);
		ACRA.getErrorReporter().setReportSender(new CrashReportSender());

		mSingleton = this;
		mActivityList = new ArrayList<WeakReference<Activity>>();
		mVersionCode = catchVersionCode();
		mVersionName = catchVersionName();
		mIMEIorMEIDorESN = catchIMEIorMEIDorESN();
		mIMEISV = catchIMEISV();
		mIMSI = catchIMSI();

		DisplayImageOptions _DefaultOptions = new DisplayImageOptions.Builder()
				// 设置下载图像过程中，显示的占位图。
				// .showImageOnLoading(R.drawable.ic_stub)
				// 设置图像的Uri为null或者长度为0的String时，显示的占位图。
				// .showImageForEmptyUri(R.drawable.ic_empty)
				// 设置图像下载/解码错误时，显示的占位图。
				// .showImageOnFail(R.drawable.ic_error)
				// 在加载图像前，ImageView的src是否被重置为null。
				.resetViewBeforeLoading(true)
				// 设置下载图像前的延迟（毫秒）。默认没有延迟。
				// .delayBeforeLoading(1000)
				// 加载的图像是否被缓存到内存中。
				.cacheInMemory(true)
				// 加载的图像是否被缓存到磁盘中。
				.cacheOnDisk(true)
				// 设置BitmapProcessor，可以在将Bitmap缓存进内存前，对其进行任意处理。
				// .preProcessor()
				// 设置BitmapProcessor，可以将Bitmap缓存进内存后，设置到ImageView前，对其进行任意处理。
				// .postProcessor()
				// 本方法传入的参数，将传给ImageDownloader的getStream(java.net.URI, Object) 方法。
				// .extraForDownloader(new Object())
				// 设置ImageLoader是否要考虑JPEG图片的EXIF参数（rotate、flip等） 。默认为false。
				// .considerExifParams(false)
				// 设置图像显示在ImageView中前的缩放系数。默认对图像进行2倍的缩小，直到比ImageView小为止。默认值适合用于AdapterView。
				.imageScaleType(ImageScaleType.NONE_SAFE)
				// .bitmapConfig(Bitmap.Config.ARGB_8888)
				// .decodingOptions()
				// 设置BitmapDisplayer，默认使用的ImageDecoder直接调用ImageView.setImageBitmap()方法。
				// .displayer(DefaultConfigurationFactory.createBitmapDisplayer())
				// .handler(new Handler())
				.build();

		ImageLoaderConfiguration _DefaultConfig = new ImageLoaderConfiguration.Builder(getApplicationContext())
				// 将图像缓存入内存时的最大分辨率，默认是设备屏幕的分辨率
				// .memoryCacheExtraOptions(720, 1280)
				// 将图像缓存入磁盘时的最大分辨率、压缩格式和压缩质量
				// .diskCacheExtraOptions(720, 1280, CompressFormat.PNG, 75, null)
				// 为下载和显示图像设置自定义的Executors，threadPoolSize(int)、threadPriority(int)、tasksProcessingOrder(QueueProcessingType)不会对自定义的Executors生效
				// .taskExecutor(Executors.newSingleThreadExecutor())
				// 为显示缓存的图像设置自定义的Executors
				// .taskExecutorForCachedImages(Executors.newSingleThreadExecutor())
				// .threadPoolSize(3)
				// .threadPriority(ImageLoaderConfiguration.Builder.DEFAULT_THREAD_PRIORITY)
				// .tasksProcessingOrder(QueueProcessingType.FIFO)
				// 当在较小的ImageView中显示图像时，会在内存中缓存一个低分辨率的缓存；然后又在较大的ImageView中显示了同一个地址的图像，这时会在内存中缓存一个高分辨率的缓存。
				// 此时，内存中就有了2个缓存，一个小，一个大。
				// 调用本方法后，后一个缓存（无论大小），都会覆盖前一个缓存。
				// .denyCacheImageMultipleSizesInMemory()
				// 设置自定义的LruMemoryCache。memoryCacheSize(int)不会对自定义的LruMemoryCache生效。
				// .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
				// 设置默认LruMemoryCache占用的内存大小。不设置的话，默认的LruMemoryCache只会占用全部程序可用内存的1/8（即Runtime.getRuntime().maxMemory()
				// / 8）。
				.memoryCacheSize(2 * 1024 * 1024)
				.memoryCacheSizePercentage(20)
				// 设置磁盘缓存，默认的磁盘缓存位置在内置存储和外置存储的“/Android/data/[app_package_name]/cache”中。discCacheSize(int)、discCacheFileCount(int)、discCacheFileNameGenerator(FileNameGenerator)不会对自定义的磁盘缓存生效。
				// .diskCache(new UnlimitedDiscCache(StorageUtils.getCacheDirectory(mContext)))
				// 设置默认磁盘缓存的大小。不设置的话，磁盘缓存的大小没有限制。
				.diskCacheSize(50 * 1024 * 1024)
				// 设置默认磁盘缓存的文件数量。不设置的话，磁盘缓存的文件数量没有限制。
				// .diskCacheFileCount(100)
				// 设置磁盘缓存文件的命名器。默认使用图像地址的HashCode作为文件名。
				// .diskCacheFileNameGenerator(DefaultConfigurationFactory.createFileNameGenerator())
				// 设置ImageDownloader，默认使用5秒连接超时，20秒下载超时的ImageDownloader。
				// .imageDownloader(DefaultConfigurationFactory.createImageDownloader(mContext))
				// 设置ImageDecoder，默认使用不记录日志的ImageDecoder。
				// .imageDecoder(DefaultConfigurationFactory.createImageDecoder(false))
				// 设置显示图像时的默认选项。
				.defaultDisplayImageOptions(_DefaultOptions)
				// 打开日志，默认是关闭的。
				// .writeDebugLogs()
				.build();
		ImageLoader.getInstance().init(_DefaultConfig);
	}

	/**
	 * 返回当前进程的ApplicationContext对象，这个Context对象是单例的、全局的。
	 * 通常，只有在需要得到生命周期绑定于整个程序进程，而不是和当前组件绑定的Context对象的时候，才使用这个方法。
	 * 例子：两种Context对象的registerReceiver(BroadcastReceiver, IntentFilter)方法有何不同：
	 * 1.如果调用组件Context对象（比如Activity）的registerReceiver()方法，receiver就被注册给了该组件。
	 * 这就意味着，在组件被销毁前，应该注销receiver。若不注销，系统在销毁组件时，会清理由此造成的内存泄漏，并在Log里记录一个错误。
	 * 因此，若使用组件Context对象注册全局receiver，当组件被销毁时，注册关系就会被移除。
	 * 2.如果调用本方法返回的ApplicationContext对象的registerReceiver()方法，receiver就会被注册成全局状态，和程序相关联。
	 * 因此，receiver永远不会被系统注销。若receiver和静态数据相关，而不是和某个组件相关，就应该这样注册。
	 * 但是，在其他地方使用全局ApplicationContext对象进行了注册绑定，却若忘记做注销、取消绑定操作的话，容易造成严重的内存泄漏。
	 * 
	 * @return 全局ApplicationContext对象
	 */
	public static ApplicationExtend getInstance()
	{
		return mSingleton;
	}

	public void addActivity(Activity pActivity)
	{
		mActivityList.add(new WeakReference<Activity>(pActivity));
	}

	public void finishAllActivity()
	{
		for (int i = mActivityList.size() - 1; i >= 0; i--) {
			if (mActivityList.get(i).get() != null) {
				mActivityList.get(i).get().finish();
			}
		}
	}

	/**
	 * 返回程序的VersionCode，若找不到VersionCode则返回-1。
	 */
	public int getVersionCode()
	{
		return mVersionCode;
	}

	/**
	 * 返回程序的VersionName，若找不到VersionName则返回""。
	 */
	public String getVersionName()
	{
		return mVersionName;
	}

	/**
	 * 返回设备的唯一ID。
	 * 
	 * @return GSM手机返回IMEI；CDMA手机返回MEID或者ESN。若找不到ID则返回""。
	 */
	public String getIMEIorMEIDorESN()
	{
		return mIMEIorMEIDorESN;
	}

	/**
	 * 返回设备的软件版本。
	 * 
	 * @return GSM手机返回IMEISV。若找不到则返回""。
	 */
	public String getIMEISV()
	{
		return mIMEISV;
	}

	/**
	 * 返回存储在SIM卡中的用户识别码。
	 * 
	 * @return GSM手机返回IMSI。若找不到则返回""。
	 */
	public String getIMSI()
	{
		return mIMSI;
	}

	protected int catchVersionCode()
	{
		try {
			PackageInfo _PackageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
			return _PackageInfo.versionCode;
		} catch (NameNotFoundException e) {
			return -1;
		}
	}

	protected String catchVersionName()
	{
		try {
			PackageInfo _PackageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
			return _PackageInfo.versionName;
		} catch (NameNotFoundException e) {
			return "";
		}
	}

	protected String catchIMEIorMEIDorESN()
	{
		TelephonyManager _TelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
		String _IMEI = _TelephonyManager.getDeviceId();

		if (TextUtils.isEmpty(_IMEI)) {
			return "";
		} else {
			return _IMEI;
		}
	}

	protected String catchIMEISV()
	{
		TelephonyManager _TelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

		String _IMEISV = _TelephonyManager.getDeviceSoftwareVersion();

		if (TextUtils.isEmpty(_IMEISV)) {
			return "";
		} else {
			return _IMEISV;
		}
	}

	protected String catchIMSI()
	{
		TelephonyManager _TelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

		String _IMSI = _TelephonyManager.getSubscriberId();

		if (TextUtils.isEmpty(_IMSI)) {
			return "";
		} else {
			return _IMSI;
		}
	}

	protected String catchPhoneNum()
	{
		TelephonyManager _TelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

		String _PhoneNum = _TelephonyManager.getLine1Number();

		if (TextUtils.isEmpty(_PhoneNum)) {
			return "";
		} else {
			return _PhoneNum;
		}
	}

	private static class CrashReportSender implements ReportSender
	{
		@Override
		public void send(CrashReportData pErrorContent) throws ReportSenderException
		{
			if (pErrorContent != null) {
				LoggerFactory.getLogger(getClass()).error("App crashed! Report:%n{}", pErrorContent);
			}
		}
	}
}
